Android 测量,布局,绘制流程解析
前言
Android 中自定义 View 常常需要覆写 onMeasure, onDraw 方法,而自定义 ViewGroup 则还需要覆写 onLayout 方法,那么这三者之间到底是如何调用的呢
源码解析基于 Android 7.1
测量,布局,绘制流程解析
触发测量布局绘制的流程是在 ViewRootImpl 中的 performTraversals 方法中,追溯到源头是由 Choreographer 类的帧回调触发的,这个方法中的代码非常多,所以在以下解析中会省略部分的代码
1 | private void performTraversals() { |
最终都是通过 mView 即 DecorView 这个真正意义上的根 view 开始测量布局绘制的流程分发
measure
View.measure 方法
1 | public final void measure(int widthMeasureSpec, int heightMeasureSpec) { |
由于 ViewGroup 中并没有覆写 measure 方法,所以在 ViewGroup 中调用子 view 的 measure 方法需要在 onMeasure 中自己完成了。
ViewGroup 中有提供 measureChildren 测量所有的子 View,measureChild 及 measureChildWithMargins 测量单个子 View 的方法,需要自定义 GroupView 时自己选择调用,千万不能忘记,还有要注意的是 View 的 visible 状态为 Gone 时应该跳过测量。系统的 ViewGroup 中都根据自己的需求实现了 onMeasure 方法,有时会测量多次,比如 RelativeLayout 需要横竖测量两次,LinearLayout 在有 weight 属性时也需要测量两次。在调用子 View 的 measure 后,就可以通过 getMeasureXX 获取测量结果了。
layout
View.layout 方法
1 | public void layout(int l, int t, int r, int b) { |
onLayout 是 ViewGroup 的属性,我们需要实现自己的逻辑去完成子 View 在 ViewGroup 中的布局,调用的是子 View 的 layout 方法
draw
View.draw 方法
1 | public void draw(Canvas canvas) { |
单纯的 draw 方法逻辑比较简单,继续追踪 dispatchDraw 方法,View 中的 dispatchDraw 是一个空方法,所以主要逻辑在 ViewGroup 中的 dispatchDraw。由于这里面的方法比较复杂,所以我们就不贴出来了,需要知道的是 dispatchDraw 里面会遍历子 view,调用的是 boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) 方法,而不是 draw(Canvas canvas)。
View.draw(canvas, parent, drawingTime) 方法
1 | boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) { |
就这样一直分发到最底层的 view 完成了绘制过程
requestLayout() & invalidate()
View.requestLayout 会触发 view 的测量布局刷新,而 invalidate 只会触发 view 的绘制
View.requestLayout 方法
1 | public void requestLayout() { |
由于 requestLayout 方法会触发 view 所在的整条链上的调用 view 去重新测量布局绘制,所以尽量避免频繁的调用该方法,比如说尽量固定 view 的大小,通过 view 的 layout 方法更改位置,在大小固定的情况下自定义 View 可以对调用 requestLayout 方法做一些限制
View.invalidate 方法
1 | /** |
View 的 invalidateInternal 会传入绘制区域,在这个方法中会调用父 ViewGroup 的 invalidateChild 的方法
ViewGroup.invalidateChild 方法
1 | public final void invalidateChild(View child, final Rect dirty) { |
而在 ViewRootImpl.invalidateChildInParent 会把 dirty 最终范围带入在触发 draw 的时候则会锁定这个画布的范围更新视图
invalidate 也是验证树层层往上传递处理标记标志位,最后由 ViewRootImpl 触发整个树的刷新,只不过由于没有标记测量和布局的标志位,所以不会触发重测量和布局
结语
其实 Android 中关于布局的测量布局和绘制是一个非常复杂的流程,由 ViewRootImpl 发起遍历树的操作,而 ViewGroup 和 View 中有各种各样的标志位来标记操作的执行。这篇文章也只是讲了其中的一小部分,也仅仅是记录了一下我的理解。